#ifndef STREAMACCESS_HPP
#define STREAMACCESS_HPP

#include "globals.hpp"
#include "typeAliases.hpp"
#include "streamUtil.hpp"
#include "streamEigen.hpp"
#include <filesystem>
#include <fstream>
#include <vector>

namespace fs = std::filesystem;

namespace hms
{

/**
 * @brief Opens a file specified by its full path for writing. Erases previous
 * content.
 * 
 * @param p The full file path.
 * @param binary Flag for whether to read the file as binary.
 * @return std::ofstream 
 */
std::ofstream getWriteFile( const fs::path& p, bool binary = false );


/**
 * @brief Opens a file specified by its full path for reading. 
 * 
 * @param p The full file path.
 * @param binary Flag for whether to read the file as binary.
 * @return std::ifstream 
 */
std::ifstream getReadFile( const fs::path& p, bool binary = false );

bool getUserChoice();


template<typename T, typename U>
using EnableIfFundamental = std::enable_if_t<std::is_fundamental_v<T>, U>;

/**
 * @brief Reads any fundamental type T from an istream. Throws with useful error
 * message on failure.
 * 
 * @tparam T The fundamental type to be read
 * @return std::enable_if_t<std::is_fundamental_v<T>> 
 */
template<typename T>
EnableIfFundamental<T,T> read( std::istream& );


template<typename T>
using EnableIfString = std::enable_if_t<std::is_same_v<T,std::string>, std::string>;

/**
 * @brief Reads a string token from a stream. Uses operator>> for strings.
 * 
 * @tparam Must be std::string.
 * @return std::string 
 */
template<typename T>
EnableIfString<T> read( std::istream& );

/**
 * @brief Reads a std::vector from an istream. The underlying type must be
 * fundamental and the size of the vector must be set.
 * 
 * @tparam T Any fundamental type.
 * @param is The istream object holding the content to be read.
 * @param v Out-parameter. Size must be set before passing it in.
 * @return EnableIfFundamental<T, std::istream&> 
 */
template<typename T>
EnableIfFundamental<T, std::istream&> read( std::istream& is, std::vector<T>& v);

/**
 * @brief Writes the content of a std::vector to an ostream. The underlying type
 * must be fundamental.
 * 
 * @tparam T 
 * @return EnableIfFundamental<T, std::ostream&> 
 */
template<typename T>
EnableIfFundamental<T, std::ostream&> write( std::ostream&, const std::vector<T>& );

/**
 * @brief Writes fundamental types to an ostream. Respects globally defined
 * precision for floating point types.
 * 
 * @tparam T 
 * @return EnableIfFundamental<T, std::ostream&> 
 */
template<typename T>
EnableIfFundamental<T, std::ostream&> write( std::ostream&, T );

/**
 * @brief Converts fundamental types to string. Sets globally defined precision
 * for floating point types and is therefore prefererable over std::to_string.
 */
template<typename T>
EnableIfFundamental<T, std::string> toString( T t );

/**
 * @brief Writes the content of a std::vector to a string. The underlying type
 * of the vector must be fundamental.
 * 
 * @tparam T 
 * @param v 
 * @return std::string 
 */
template<typename T>
EnableIfFundamental<T, std::string> toString( const std::vector<T>& v );



/**
 * @brief Reads the next token of only alphabetical characters from an istream.
 * 
 * @return std::string 
 */
std::string getWord( std::istream& );

} // end namespace hms

#include "streamAccess.tpp"

#endif